//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
/*

===== h_battery.cpp ========================================================

  battery-related code

*/

#include "cbase.h"
#include "gamerules.h"
#include "player.h"
#include "engine/IEngineSound.h"
#include "in_buttons.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

static ConVar	sk_suitcharger( "sk_suitcharger","0" );
static ConVar	sk_suitcharger_citadel( "sk_suitcharger_citadel","0" );
static ConVar	sk_suitcharger_citadel_maxarmor( "sk_suitcharger_citadel_maxarmor","0" );

#define SF_CITADEL_RECHARGER	0x2000
#define SF_KLEINER_RECHARGER	0x4000 // Gives only 25 health

class CRecharge : public CBaseToggle
{
public:
	DECLARE_CLASS( CRecharge, CBaseToggle );

	void Spawn( );
	bool CreateVPhysics();
	int DrawDebugTextOverlays(void);
	void Off(void);
	void Recharge(void);
	bool KeyValue( const char *szKeyName, const char *szValue );
	void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	virtual int	ObjectCaps( void ) { return (BaseClass::ObjectCaps() | FCAP_CONTINUOUS_USE); }

private:
	void InputRecharge( inputdata_t &inputdata );
	
	float MaxJuice() const;
	void UpdateJuice( int newJuice );

	DECLARE_DATADESC();

	float	m_flNextCharge; 
	int		m_iReactivate ; // DeathMatch Delay until reactvated
	int		m_iJuice;
	int		m_iOn;			// 0 = off, 1 = startup, 2 = going
	float   m_flSoundTime;
	
	int		m_nState;
	
	COutputFloat m_OutRemainingCharge;
	COutputEvent m_OnHalfEmpty;
	COutputEvent m_OnEmpty;
	COutputEvent m_OnFull;
	COutputEvent m_OnPlayerUse;
};

BEGIN_DATADESC( CRecharge )

	DEFINE_FIELD( m_flNextCharge, FIELD_TIME ),
	DEFINE_FIELD( m_iReactivate, FIELD_INTEGER),
	DEFINE_FIELD( m_iJuice, FIELD_INTEGER),
	DEFINE_FIELD( m_iOn, FIELD_INTEGER),
	DEFINE_FIELD( m_flSoundTime, FIELD_TIME ),
	DEFINE_FIELD( m_nState, FIELD_INTEGER ),

	// Function Pointers
	DEFINE_FUNCTION( Off ),
	DEFINE_FUNCTION( Recharge ),

	DEFINE_OUTPUT(m_OutRemainingCharge, "OutRemainingCharge"),
	DEFINE_OUTPUT(m_OnHalfEmpty, "OnHalfEmpty" ),
	DEFINE_OUTPUT(m_OnEmpty, "OnEmpty" ),
	DEFINE_OUTPUT(m_OnFull, "OnFull" ),
	DEFINE_OUTPUT(m_OnPlayerUse, "OnPlayerUse" ),

	DEFINE_INPUTFUNC( FIELD_VOID, "Recharge", InputRecharge ),
	
END_DATADESC()


LINK_ENTITY_TO_CLASS(func_recharge, CRecharge);


bool CRecharge::KeyValue( const char *szKeyName, const char *szValue )
{
	if (	FStrEq(szKeyName, "style") ||
				FStrEq(szKeyName, "height") ||
				FStrEq(szKeyName, "value1") ||
				FStrEq(szKeyName, "value2") ||
				FStrEq(szKeyName, "value3"))
	{
	}
	else if (FStrEq(szKeyName, "dmdelay"))
	{
		m_iReactivate = atoi(szValue);
	}
	else
	{
		return BaseClass::KeyValue( szKeyName, szValue );
	}

	return true;
}

void CRecharge::Spawn()
{
	Precache( );

	SetSolid( SOLID_BSP );
	SetMoveType( MOVETYPE_PUSH );

	SetModel( STRING( GetModelName() ) );

	UpdateJuice( MaxJuice() );

	m_nState = 0;			

	CreateVPhysics();
}

bool CRecharge::CreateVPhysics()
{
	VPhysicsInitStatic();
	return true;
}

int CRecharge::DrawDebugTextOverlays(void) 
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		char tempstr[512];
		Q_snprintf(tempstr,sizeof(tempstr),"Charge left: %i", m_iJuice );
		EntityText(text_offset,tempstr,0);
		text_offset++;
	}
	return text_offset;
}


//-----------------------------------------------------------------------------
// Max juice for recharger
//-----------------------------------------------------------------------------
float CRecharge::MaxJuice()	const
{
	if ( HasSpawnFlags( SF_CITADEL_RECHARGER ) )
	{
		return sk_suitcharger_citadel.GetFloat();
	}
	
	return sk_suitcharger.GetFloat();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : newJuice - 
//-----------------------------------------------------------------------------
void CRecharge::UpdateJuice( int newJuice )
{
	bool reduced = newJuice < m_iJuice;
	if ( reduced )
	{
		// Fire 1/2 way output and/or empyt output
		int oneHalfJuice = (int)(MaxJuice() * 0.5f);
		if ( newJuice <= oneHalfJuice && m_iJuice > oneHalfJuice )
		{
			m_OnHalfEmpty.FireOutput( this, this );
		}

		if ( newJuice <= 0 )
		{
			m_OnEmpty.FireOutput( this, this );
		}
	}
	else if ( newJuice != m_iJuice &&
		newJuice == (int)MaxJuice() )
	{
		m_OnFull.FireOutput( this, this );
	}
	m_iJuice = newJuice;
}

void CRecharge::InputRecharge( inputdata_t &inputdata )
{
	Recharge();
}

void CRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{ 
	// if it's not a player, ignore
	if ( !pActivator || !pActivator->IsPlayer() )
		return;

	// Only usable if you have the HEV suit on
	if ( !((CBasePlayer *)pActivator)->IsSuitEquipped() )
	{
		if (m_flSoundTime <= gpGlobals->curtime)
		{
			m_flSoundTime = gpGlobals->curtime + 0.62;
			EmitSound( "SuitRecharge.Deny" );
		}
		return;
	}

	// if there is no juice left, turn it off
	if (m_iJuice <= 0)
	{
		m_nState = 1;			
		Off();
	}

	// if the player doesn't have the suit, or there is no juice left, make the deny noise
	if ( m_iJuice <= 0 )
	{
		if (m_flSoundTime <= gpGlobals->curtime)
		{
			m_flSoundTime = gpGlobals->curtime + 0.62;
			EmitSound( "SuitRecharge.Deny" );
		}
		return;
	}

	SetNextThink( gpGlobals->curtime + 0.25 );
	SetThink(&CRecharge::Off);

	// Time to recharge yet?
	if (m_flNextCharge >= gpGlobals->curtime)
		return;

	// Make sure that we have a caller
	if (!pActivator)
		return;

	m_hActivator = pActivator;

	//only recharge the player

	if (!m_hActivator->IsPlayer() )
		return;
	
	// Play the on sound or the looping charging sound
	if (!m_iOn)
	{
		m_iOn++;
		EmitSound( "SuitRecharge.Start" );
		m_flSoundTime = 0.56 + gpGlobals->curtime;

		m_OnPlayerUse.FireOutput( pActivator, this );
	}

	if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->curtime))
	{
		m_iOn++;
		CPASAttenuationFilter filter( this, "SuitRecharge.ChargingLoop" );
		filter.MakeReliable();
		EmitSound( filter, entindex(), "SuitRecharge.ChargingLoop" );
	}

	CBasePlayer *pl = (CBasePlayer *) m_hActivator.Get();

	// charge the player
	int nMaxArmor = 100;
	int nIncrementArmor = 1;
	if ( HasSpawnFlags(	SF_CITADEL_RECHARGER ) )
	{
		nMaxArmor = sk_suitcharger_citadel_maxarmor.GetInt();
		nIncrementArmor = 10;

		// Also give health for the citadel version.
		if( pActivator->GetHealth() < pActivator->GetMaxHealth() )
		{
			pActivator->TakeHealth( 5, DMG_GENERIC );
		}
	}

	if (pl->ArmorValue() < nMaxArmor)
	{
		UpdateJuice( m_iJuice - nIncrementArmor );
		pl->IncrementArmorValue( nIncrementArmor, nMaxArmor );
	}

	// Send the output.
	float flRemaining = m_iJuice / MaxJuice();
	m_OutRemainingCharge.Set(flRemaining, pActivator, this);

	// govern the rate of charge
	m_flNextCharge = gpGlobals->curtime + 0.1;
}

void CRecharge::Recharge(void)
{
	UpdateJuice( MaxJuice() );
	m_nState = 0;			
	SetThink( &CRecharge::SUB_DoNothing );
}

void CRecharge::Off(void)
{
	// Stop looping sound.
	if (m_iOn > 1)
	{
		StopSound( "SuitRecharge.ChargingLoop" );
	}

	m_iOn = 0;

	if ((!m_iJuice) &&  ( ( m_iReactivate = g_pGameRules->FlHEVChargerRechargeTime() ) > 0) )
	{
		SetNextThink( gpGlobals->curtime + m_iReactivate );
		SetThink(&CRecharge::Recharge);
	}
	else
	{
		SetThink( NULL );
	}
}


//NEW
class CNewRecharge : public CBaseAnimating
{
public:
	DECLARE_CLASS( CNewRecharge, CBaseAnimating );

	void Spawn( );
	bool CreateVPhysics();
	int DrawDebugTextOverlays(void);
	void Off(void);
	void Recharge(void);
	bool KeyValue( const char *szKeyName, const char *szValue );
	void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	virtual int	ObjectCaps( void ) { return (BaseClass::ObjectCaps() | m_iCaps ); }

	void SetInitialCharge( void );

private:
	void InputRecharge( inputdata_t &inputdata );
	void InputSetCharge( inputdata_t &inputdata );
	float MaxJuice() const;
	void UpdateJuice( int newJuice );
	void Precache( void );

	DECLARE_DATADESC();

	float	m_flNextCharge; 
	int		m_iReactivate ; // DeathMatch Delay until reactvated
	int		m_iJuice;
	int		m_iOn;			// 0 = off, 1 = startup, 2 = going
	float   m_flSoundTime;
	
	int		m_nState;
	int		m_iCaps;
	int		m_iMaxJuice;
	
	COutputFloat m_OutRemainingCharge;
	COutputEvent m_OnHalfEmpty;
	COutputEvent m_OnEmpty;
	COutputEvent m_OnFull;
	COutputEvent m_OnPlayerUse;

	virtual void StudioFrameAdvance ( void );
	float m_flJuice;
};

BEGIN_DATADESC( CNewRecharge )

	DEFINE_FIELD( m_flNextCharge, FIELD_TIME ),
	DEFINE_FIELD( m_iReactivate, FIELD_INTEGER),
	DEFINE_FIELD( m_iJuice, FIELD_INTEGER),
	DEFINE_FIELD( m_iOn, FIELD_INTEGER),
	DEFINE_FIELD( m_flSoundTime, FIELD_TIME ),
	DEFINE_FIELD( m_nState, FIELD_INTEGER ),
	DEFINE_FIELD( m_iCaps, FIELD_INTEGER ),
	DEFINE_FIELD( m_iMaxJuice, FIELD_INTEGER ),

	// Function Pointers
	DEFINE_FUNCTION( Off ),
	DEFINE_FUNCTION( Recharge ),

	DEFINE_OUTPUT(m_OutRemainingCharge, "OutRemainingCharge"),
	DEFINE_OUTPUT(m_OnHalfEmpty, "OnHalfEmpty" ),
	DEFINE_OUTPUT(m_OnEmpty, "OnEmpty" ),
	DEFINE_OUTPUT(m_OnFull, "OnFull" ),
	DEFINE_OUTPUT(m_OnPlayerUse, "OnPlayerUse" ),
	DEFINE_FIELD( m_flJuice, FIELD_FLOAT ),

	DEFINE_INPUTFUNC( FIELD_VOID, "Recharge", InputRecharge ),
	DEFINE_INPUTFUNC( FIELD_INTEGER, "SetCharge", InputSetCharge ),
	
END_DATADESC()


LINK_ENTITY_TO_CLASS( item_suitcharger, CNewRecharge);

#define HEALTH_CHARGER_MODEL_NAME "models/props_combine/suit_charger001.mdl"
#define CHARGE_RATE 0.25f
#define CHARGES_PER_SECOND 1 / CHARGE_RATE
#define CITADEL_CHARGES_PER_SECOND 10 / CHARGE_RATE
#define CALLS_PER_SECOND 7.0f * CHARGES_PER_SECOND


bool CNewRecharge::KeyValue( const char *szKeyName, const char *szValue )
{
	if (	FStrEq(szKeyName, "style") ||
				FStrEq(szKeyName, "height") ||
				FStrEq(szKeyName, "value1") ||
				FStrEq(szKeyName, "value2") ||
				FStrEq(szKeyName, "value3"))
	{
	}
	else if (FStrEq(szKeyName, "dmdelay"))
	{
		m_iReactivate = atoi(szValue);
	}
	else
	{
		return BaseClass::KeyValue( szKeyName, szValue );
	}

	return true;
}

void CNewRecharge::Precache( void )
{
	PrecacheModel( HEALTH_CHARGER_MODEL_NAME );

	PrecacheScriptSound( "SuitRecharge.Deny" );
	PrecacheScriptSound( "SuitRecharge.Start" );
	PrecacheScriptSound( "SuitRecharge.ChargingLoop" );

}

void CNewRecharge::SetInitialCharge( void )
{
	if ( HasSpawnFlags( SF_KLEINER_RECHARGER ) )
	{
		// The charger in Kleiner's lab.
		m_iMaxJuice =  25.0f;
		return;
	}

	if ( HasSpawnFlags( SF_CITADEL_RECHARGER ) )
	{
		m_iMaxJuice =  sk_suitcharger_citadel.GetFloat();
		return;
	}

	m_iMaxJuice =  sk_suitcharger.GetFloat();
}

void CNewRecharge::Spawn()
{
	Precache( );

	SetMoveType( MOVETYPE_NONE );
	SetSolid( SOLID_VPHYSICS );
	CreateVPhysics();

	SetModel( HEALTH_CHARGER_MODEL_NAME );
	AddEffects( EF_NOSHADOW );

	ResetSequence( LookupSequence( "idle" ) );

	SetInitialCharge();

	UpdateJuice( MaxJuice() );

	m_nState = 0;		
	m_iCaps	= FCAP_CONTINUOUS_USE;

	CreateVPhysics();

	m_flJuice = m_iJuice;

	m_iReactivate = 0;

	SetCycle( 1.0f - ( m_flJuice / MaxJuice() ) );
}

bool CNewRecharge::CreateVPhysics()
{
	VPhysicsInitStatic();
	return true;
}

int CNewRecharge::DrawDebugTextOverlays(void) 
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		char tempstr[512];
		Q_snprintf(tempstr,sizeof(tempstr),"Charge left: %i", m_iJuice );
		EntityText(text_offset,tempstr,0);
		text_offset++;
	}
	return text_offset;
}

void CNewRecharge::StudioFrameAdvance( void )
{
	m_flPlaybackRate = 0;

	float flMaxJuice = MaxJuice() + 0.1f;
	float flNewJuice = 1.0f - (float)( m_flJuice / flMaxJuice );

	SetCycle( flNewJuice );
//	Msg( "Cycle: %f - Juice: %d - m_flJuice :%f - Interval: %f\n", (float)GetCycle(), (int)m_iJuice, (float)m_flJuice, GetAnimTimeInterval() );

	if ( !m_flPrevAnimTime )
	{
		m_flPrevAnimTime = gpGlobals->curtime;
	}

	// Latch prev
	m_flPrevAnimTime = m_flAnimTime;
	// Set current
	m_flAnimTime = gpGlobals->curtime;
}



//-----------------------------------------------------------------------------
// Max juice for recharger
//-----------------------------------------------------------------------------
float CNewRecharge::MaxJuice()	const
{
	return m_iMaxJuice;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : newJuice - 
//-----------------------------------------------------------------------------
void CNewRecharge::UpdateJuice( int newJuice )
{
	bool reduced = newJuice < m_iJuice;
	if ( reduced )
	{
		// Fire 1/2 way output and/or empyt output
		int oneHalfJuice = (int)(MaxJuice() * 0.5f);
		if ( newJuice <= oneHalfJuice && m_iJuice > oneHalfJuice )
		{
			m_OnHalfEmpty.FireOutput( this, this );
		}

		if ( newJuice <= 0 )
		{
			m_OnEmpty.FireOutput( this, this );
		}
	}
	else if ( newJuice != m_iJuice &&
		newJuice == (int)MaxJuice() )
	{
		m_OnFull.FireOutput( this, this );
	}
	m_iJuice = newJuice;
}

void CNewRecharge::InputRecharge( inputdata_t &inputdata )
{
	Recharge();
}

void CNewRecharge::InputSetCharge( inputdata_t &inputdata )
{
	ResetSequence( LookupSequence( "idle" ) );

	int iJuice = inputdata.value.Int();

	m_flJuice = m_iMaxJuice = m_iJuice = iJuice;
	StudioFrameAdvance();
}

void CNewRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{ 
	// if it's not a player, ignore
	if ( !pActivator || !pActivator->IsPlayer() )
		return;

	CBasePlayer *pPlayer = static_cast<CBasePlayer *>(pActivator);

	// Reset to a state of continuous use.
	m_iCaps = FCAP_CONTINUOUS_USE;

	if ( m_iOn )
	{
		float flCharges = CHARGES_PER_SECOND;
		float flCalls = CALLS_PER_SECOND;

		if ( HasSpawnFlags( SF_CITADEL_RECHARGER ) )
			 flCharges = CITADEL_CHARGES_PER_SECOND;

		m_flJuice -= flCharges / flCalls;		
		StudioFrameAdvance();
	}

	// Only usable if you have the HEV suit on
	if ( !pPlayer->IsSuitEquipped() )
	{
		if (m_flSoundTime <= gpGlobals->curtime)
		{
			m_flSoundTime = gpGlobals->curtime + 0.62;
			EmitSound( "SuitRecharge.Deny" );
		}
		return;
	}

	// if there is no juice left, turn it off
	if ( m_iJuice <= 0 )
	{
		// Start our deny animation over again
		ResetSequence( LookupSequence( "emptyclick" ) );
		
		m_nState = 1;
		
		// Shut off
		Off();
		
		// Play a deny sound
		if ( m_flSoundTime <= gpGlobals->curtime )
		{
			m_flSoundTime = gpGlobals->curtime + 0.62;
			EmitSound( "SuitRecharge.Deny" );
		}

		return;
	}

	// Get our maximum armor value
	int nMaxArmor = 100;
	if ( HasSpawnFlags(	SF_CITADEL_RECHARGER ) )
	{
		nMaxArmor = sk_suitcharger_citadel_maxarmor.GetInt();
	}
	
	int nIncrementArmor = 1;

	// The citadel charger gives more per charge and also gives health
	if ( HasSpawnFlags(	SF_CITADEL_RECHARGER ) )
	{
		nIncrementArmor = 10;
		
#ifdef HL2MP
		nIncrementArmor = 2;
#endif

		// Also give health for the citadel version.
		if ( pActivator->GetHealth() < pActivator->GetMaxHealth() && m_flNextCharge < gpGlobals->curtime )
		{
			pActivator->TakeHealth( 5, DMG_GENERIC );
		}
	}

	// If we're over our limit, debounce our keys
	if ( pPlayer->ArmorValue() >= nMaxArmor)
	{
		// Citadel charger must also be at max health
		if ( !HasSpawnFlags(SF_CITADEL_RECHARGER) || ( HasSpawnFlags( SF_CITADEL_RECHARGER ) && pActivator->GetHealth() >= pActivator->GetMaxHealth() ) )
		{
			// Make the user re-use me to get started drawing health.
			pPlayer->m_afButtonPressed &= ~IN_USE;
			m_iCaps = FCAP_IMPULSE_USE;
			
			EmitSound( "SuitRecharge.Deny" );
			return;
		}
	}

	// This is bumped out if used within the time period
	SetNextThink( gpGlobals->curtime + CHARGE_RATE );
	SetThink( &CNewRecharge::Off );

	// Time to recharge yet?
	if ( m_flNextCharge >= gpGlobals->curtime )
		return;
	
	// Play the on sound or the looping charging sound
	if ( !m_iOn )
	{
		m_iOn++;
		EmitSound( "SuitRecharge.Start" );
		m_flSoundTime = 0.56 + gpGlobals->curtime;

		m_OnPlayerUse.FireOutput( pActivator, this );
	}

	if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->curtime))
	{
		m_iOn++;
		CPASAttenuationFilter filter( this, "SuitRecharge.ChargingLoop" );
		filter.MakeReliable();
		EmitSound( filter, entindex(), "SuitRecharge.ChargingLoop" );
	}

	// Give armor if we need it
	if ( pPlayer->ArmorValue() < nMaxArmor )
	{
		UpdateJuice( m_iJuice - nIncrementArmor );
		pPlayer->IncrementArmorValue( nIncrementArmor, nMaxArmor );
	}

	// Send the output.
	float flRemaining = m_iJuice / MaxJuice();
	m_OutRemainingCharge.Set(flRemaining, pActivator, this);

	// govern the rate of charge
	m_flNextCharge = gpGlobals->curtime + 0.1;
}

void CNewRecharge::Recharge(void)
{
	EmitSound( "SuitRecharge.Start" );
	ResetSequence( LookupSequence( "idle" ) );

	UpdateJuice( MaxJuice() );

	m_nState = 0;		
	m_flJuice = m_iJuice;
	m_iReactivate = 0;
	StudioFrameAdvance();

	SetThink( &CNewRecharge::SUB_DoNothing );
}

void CNewRecharge::Off(void)
{
	// Stop looping sound.
	if (m_iOn > 1)
	{
		StopSound( "SuitRecharge.ChargingLoop" );
	}
	
	if ( m_nState == 1 )
	{
		SetCycle( 1.0f );
	}

	m_iOn = 0;
	m_flJuice = m_iJuice;

	if ( m_iReactivate == 0 )
	{
		if ((!m_iJuice) && g_pGameRules->FlHEVChargerRechargeTime() > 0 )
		{
			if ( HasSpawnFlags( SF_CITADEL_RECHARGER ) )
			{
				m_iReactivate = g_pGameRules->FlHEVChargerRechargeTime() * 2;
			}
			else
			{
				m_iReactivate = g_pGameRules->FlHEVChargerRechargeTime();
			}
			SetNextThink( gpGlobals->curtime + m_iReactivate );
			SetThink(&CNewRecharge::Recharge);
		}
		else
		{
			SetThink( NULL );
		}
	}
}
